package de.hsb.ai.agent.misterx.minimax;

import game.Move;
import jade.core.AID;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import org.apache.commons.collections.MultiHashMap;
import org.apache.commons.collections.MultiMap;
import de.hsb.ai.agent.misterx.brain.Brain;
import de.hsb.ai.agent.misterx.pathfinding.Pathfinding;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeEdge;

/**
 * Hilfsklasse mit statischen Methoden für die Alpha-Beat Suche
 * @author Dennis
 *
 */

@SuppressWarnings("deprecation")
public class AlphaBetaHelper {
	
	//Nodes in denen man schnell umzingelt wird
	protected final static int[] TRAP_NODES = {6, 4, 2, 162, 5, 7};
	
	/**
	 * Fügt einem Node die möglichen Züge eines Detectives hinzu
	 * @param successorNode Node dem die Optionen der Detectives hinzugefügt werden sollen
	 * @return successorNode Node mit hinzugefügten Informationen
	 */
	public static Node getDetectiveStates(Node successorNode) {
		List<AID> detectives = Brain.getEnemyAIDs();
		for(AID detective : detectives){
			Move move = Brain.HISTORYCAPTURER.getLastPlayerMove(detective);
			successorNode._detectiveStates.put(detective, move.getNode());
		}
		return successorNode;
	}

	/**
	 * Ermittelt alle möglichen Folgenodes eines Nodes (mögliche Züge)
	 * @param state Node(Spielnode) von dem die Nachbarnodes ermittelt werden soll
	 * @return Liste mit Nachbarnodes
	 */
	public static ArrayList<Integer> getMoveOptions(int state) {
		ArrayList<Integer> options = new ArrayList<Integer>();
		Set<ScotlandJadeEdge> scotlandJadeEdges;
		scotlandJadeEdges = Brain.SCOTLANDJADEMAP.edgesOf(Brain.SCOTLANDJADEMAP.get(state));
		for(ScotlandJadeEdge scotlandJadeEdge : scotlandJadeEdges) {
			options.add(scotlandJadeEdge.getTargetIndex(state));		
		}
		return options;
	}
	
	/**
	 * Gibt alle möglichen Züge aller Detectives zurück
	 * @param detectiveStates Standorte der Detectives
	 * @return Map mit allen möglichen Folge-Standorten der Detectives
	 */
	@SuppressWarnings("deprecation")
	public static MultiMap getDetectiveMoveOptions(HashMap<AID, Integer> detectiveStates) {
		//ArrayList<Integer> options = new ArrayList<Integer>();
		MultiMap options = new MultiHashMap();
		List<AID> detectives = Brain.getEnemyAIDs();
		for(AID detective : detectives){
			Set<ScotlandJadeEdge> scotlandJadeEdges;
			scotlandJadeEdges = Brain.SCOTLANDJADEMAP.edgesOf(Brain.SCOTLANDJADEMAP.get(detectiveStates.get(detective)));
			for(ScotlandJadeEdge scotlandJadeEdge : scotlandJadeEdges) {
				options.put(scotlandJadeEdge.getTargetIndex(detectiveStates.get(detective)), detective);
			}
		}
		return options;
	}
	
	/**
	 * Prüft ob ein Node zu den "schlechten" Nodes gehört
	 * @param node der zu überprüfende Node
	 * @return true, wenn der Node "schlect" ist, false sonst
	 */
	protected static boolean isTrapNode(Node node){
		return isTrapNode(node._mrXState);
	}
	
	/**
	 * Prüft ob ein Node zu den "schlechten" Nodes gehört
	 * @param Node-Index auf den MrX gehen möchte
	 * @return true, wenn der Node "schlecht" ist, false sonst
	 */
	protected static boolean isTrapNode(int mrXstate){
		//Befindet sich MrX auf einem DangerNode?
		for(int i=0; i < AlphaBetaHelper.TRAP_NODES.length; i++){
			if(mrXstate == AlphaBetaHelper.TRAP_NODES[i]){
				return true;
			}
		}
		return false;
	}

	/**
	 * Sucht den besten Node anhand bestVelaue. Wenn mehrere Nodes diesen Wert besitzen, wird zufällig einer ausgewählt
	 * @param node CurrentState
	 * @param bestValue bester Wert der durch Minimax ermittelt wurde
	 * @return NodeIndex auf den sich Mrx als nächsten bewegen soll
	 */
	public static int getSuccessorWithBestValue(Node node, int bestValue) {
		ArrayList<Node> nodes = new ArrayList<Node>();
		//Nodes suchen zu den der bestValue passt
		for(int i = 0; i < node._childs.size(); i++) {
			Node successorNode = node._childs.get(i);
			
			//suche alle Node mit bestValue, ingoniriere DangerNodes
			if(successorNode._cost == bestValue) {
				if(AlphaBetaHelper.isSaveNode(successorNode)){
					nodes.add(successorNode);
				}
			}
			
		}
		
		if(nodes.size() > 1){
			int range = nodes.size();
			int random = (int) ((Math.random()*range)+1);
			return nodes.get(random-1)._mrXState;
		}
		else{
			if(nodes.size()==1){
				return nodes.get(0)._mrXState;
			}else{
				return 1;
			}
		}
	}
	
	/**
	 * Prüft ob ein Node, auf den man sich bewegen möchte sicher ist, also von keinem anderen Spieler erreichbar ist und nicht zu den "schlechten" Nodes gehört
	 * @param node der zu überprüfende Node
	 * @return true, wenn der Node sicher ist, false sonst
	 */
	protected static boolean isSaveNode(Node node){
		boolean save = true;
		if(isTrapNode(node)){
			save = false;
		}
		else if(!isNodeSave(node._mrXState)){
			save = false;
		}
		return save;
	}

	/**
	 * Hier wird zusätzlich nochmal verhindert, dass MrX einen Node auswählt, auf dem ein Detective zugriff hat
	 * @param node Node der nach Sicherheit überprüft werden soll
	 * @return true, wenn sicher, anonsten false
	 */
	public static boolean isNodeSave(int mrXState) {
		Pathfinding pathfinder = new Pathfinding();
		List<AID> detectives = Brain.getEnemyAIDs();
		for(AID detective : detectives){
			int pos = Brain.HISTORYCAPTURER.getLastPlayerMove(detective).getNode();
			List<de.hsb.ai.agent.misterx.pathfinding.Node> nodes = pathfinder.getPath(mrXState, pos);
			
			if(nodes != null){
				if(nodes.size()<=2){	
					//System.out.println("Current Node: " + Brain.HISTORYCAPTURER.getLastPlayerMove(Brain.getOwnAID()) + " Detective Node: " + pos + " Node: " + mrXState + " is NOT save");
					return false;
				}else{
					if(isTrapNode(mrXState)){
						//System.out.println("Current Node: " + Brain.HISTORYCAPTURER.getLastPlayerMove(Brain.getOwnAID()) + " Detective Node: " + pos + " Node: " + mrXState + " is NOT save");
						return false;
					}else{
						//System.out.println("Current Node: " + Brain.HISTORYCAPTURER.getLastPlayerMove(Brain.getOwnAID()) + " Detective Node: " + pos + " Node: " + mrXState + " is save");
				
					}
				}
			}
		}
		return true;
	}
	
	/**
	 * berechnet den UtitlityValue für den Node
	 * @param node Node für den der UtilityValue ermittelt werden soll
	 * @return UtilityValue
	 */
	protected static int getUtilityValue(Node node){
		int uv = 0;
		Pathfinding pathfinder = new Pathfinding();
		Vector<Integer> distances = new Vector<Integer>();
		
		//Befindet sich MrX auf einem DangerNode?
		for(int i=0; i<AlphaBetaHelper.TRAP_NODES.length; i++){
			if(node._mrXState == AlphaBetaHelper.TRAP_NODES[i]){
				uv = 0;
				return uv;
			}
		}
		
		//alle Distanzen von den Detectives zu MrX ermitteln
		List<AID> detectives = Brain.getEnemyAIDs();
		for(AID detective : detectives){
			int pos = node._detectiveStates.get(detective);
			List<de.hsb.ai.agent.misterx.pathfinding.Node> nodes = pathfinder.getPath(node._mrXState, pos);
			
			//Distanz = Anzahl der Nodes - 1 = Anzahl der Züge um zu dem Node zu gelangen
			int distance = 0;
			if(nodes == null){
				distance = 0;
			} else {
				distance = nodes.size() - 1;
			}
			distances.add(distance);
		}
		
		//Distanzen als Kosten aufsummieren
		for(int dist : distances){
			if(dist <= 1){ 	//Distanz von <= 1 ist sehr schlecht für MrX, Kosten daher auf 0 setzen;
				uv = 0;
				return uv;
			}else{
				uv += dist;
			}
		}
		
		return uv;
		
	}
	
}
